/**
 *  TextMash - simple IDE for Clojure
 *  
 *  Copyright (C) 2010 Aleksander Naszko
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package pl.olek.clojure;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;

/**
 * 
 * @author anaszko
 */
public class OutputInputStream extends InputStream {

	static class Slice {
		byte[] buf;
		int count;
	}

	static class Buffer extends ByteArrayOutputStream {
		public Slice getSlice() {
			Slice s = new Slice();
			s.buf = buf;
			s.count = count;
			return s;
		}
	}

	final LinkedList<Slice> lines = new LinkedList<Slice>();
	boolean closed;
	Slice currentLine;
	int pos;
	OutputStream output;
	Buffer buffer;

	public OutputInputStream() {

		output = new OutputStream() {

			@Override
			public synchronized void write(int b) throws IOException {
				if (buffer == null) {
					buffer = new Buffer();
				}
				buffer.write(b);
			}

			@Override
			public synchronized void write(byte[] b, int off, int len)
					throws IOException {
				if (buffer == null) {
					buffer = new Buffer();
				}
				buffer.write(b, off, len);
			}

			@Override
			public synchronized void close() throws IOException {
				flush();
			}

			@Override
			public synchronized void flush() throws IOException {
				if (buffer != null) {
					synchronized (lines) {
						lines.add(buffer.getSlice());
					}
					buffer = null;
				}
			}
		};
	}

	public OutputStream getOutput() {
		return output;
	}

	@Override
	public synchronized int read(byte[] b, int off, int len) throws IOException {
		int res;
		if (currentLine == null
				|| (res = Math.min(currentLine.count - pos, len)) == 0) {
			int r = read();
			if (r == -1) {
				return -1;
			}
			b[off] = (byte) r;
			return 1;
		}
		System.arraycopy(currentLine, pos, b, off, res);
		pos += res;
		return res;
	}

	@Override
	public synchronized int available() throws IOException {
		int available = 0;
		if (currentLine != null) {
			available = currentLine.count - pos;
		}
		if (available == 0) {
			synchronized (lines) {
				available = lines.isEmpty() ? 0 : 1;
			}
		}
		return available;
	}

	@Override
	public synchronized void close() throws IOException {
		closed = true;
		notify();
	}

	@Override
	public synchronized int read() throws IOException {

		try {
			for (;;) {
				if (currentLine == null || currentLine.count == pos) {
					currentLine = null;
					synchronized (lines) {
						if (lines.size() > 0) {
							currentLine = lines.removeFirst();
							pos = 0;
						}
					}
				}

				if (currentLine == null) {
					if (closed) {
						return -1;
					}
					wait();
				} else {
					break;
				}
			}

			return currentLine.buf[pos++];

		} catch (Exception e) {
			throw new IOException(e);
		}
	}
}
